
package com.huaweicloud.saaslanding.install.applicationservice.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.huaweicloud.saaslanding.basic.context.ContextUtil;
import com.huaweicloud.saaslanding.basic.database.mybatis.conditions.query.QueryWrap;
import com.huaweicloud.saaslanding.basic.enumeration.AppArchitectureEnum;
import com.huaweicloud.saaslanding.basic.enumeration.FileSystemTypeEnum;
import com.huaweicloud.saaslanding.basic.enumeration.obs.ObsStrategyEnum;
import com.huaweicloud.saaslanding.basic.exception.BizException;
import com.huaweicloud.saaslanding.basic.exception.code.ExceptionCode;
import com.huaweicloud.saaslanding.basic.utils.BeanPlusUtil;
import com.huaweicloud.saaslanding.basic.utils.EncryptionUtil;
import com.huaweicloud.saaslanding.dao.common.entity.app.AppTemplate;
import com.huaweicloud.saaslanding.dao.common.entity.deployedresource.ObsResource;
import com.huaweicloud.saaslanding.dao.common.entity.security.WorkSecretKey;
import com.huaweicloud.saaslanding.dao.common.entity.tenant.SubscribedApp;
import com.huaweicloud.saaslanding.dao.common.repository.app.AppTemplateBaseMapper;
import com.huaweicloud.saaslanding.dao.common.repository.cloudservice.ObsInfoBaseMapper;
import com.huaweicloud.saaslanding.dao.common.repository.deployedresource.ObsResourceBaseMapper;
import com.huaweicloud.saaslanding.dao.common.repository.security.WorkSecretKeyBaseMapper;
import com.huaweicloud.saaslanding.dao.common.repository.tenant.SubscribedAppBaseMapper;
import com.huaweicloud.saaslanding.dao.common.view.ObsInfoView;
import com.huaweicloud.saaslanding.huawei.obs.ObsApi;
import com.huaweicloud.saaslanding.huawei.obs.constant.ObsConstant;
import com.huaweicloud.saaslanding.huawei.obs.dto.ObsAuthDto;
import com.huaweicloud.saaslanding.huawei.obs.enumeration.FileSizeEnum;
import com.huaweicloud.saaslanding.huawei.obs.enumeration.FileTypeEnum;
import com.huaweicloud.saaslanding.install.applicationservice.ObsService;
import com.huaweicloud.saaslanding.install.properties.Endpoint;
import com.huaweicloud.saaslanding.install.properties.Iam;
import com.huaweicloud.saaslanding.install.properties.Obs;

import cn.hutool.core.collection.CollectionUtil;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ClassPathResource;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * 功能描述
 *
 * @since 2022-11-24
 */
@Slf4j
@Service
public class ObsServiceImpl implements ObsService {
    @Resource
    private ObsInfoBaseMapper obsInfoBaseMapper;

    @Resource
    private AppTemplateBaseMapper appTemplateBaseMapper;

    @Resource
    private WorkSecretKeyBaseMapper workSecretKeyBaseMapper;

    @Resource
    private SubscribedAppBaseMapper subscribedAppBaseMapper;

    @Resource
    private ObsResourceBaseMapper obsResourceBaseMapper;

    @Resource
    private ObsApi obsApi;

    @Resource
    private Obs obs;

    @Resource
    private Iam iam;

    @Resource
    private Endpoint endpoint;

    @Value("${saaslanding.platformAppName}")
    private String platformAppName;

    /**
     * 设置应用初始Logo
     */
    @Override
    public void initLogo() {
        log.info("开始设置应用初始Logo...");
        List<AppTemplate> appTemplateList =
            appTemplateBaseMapper.selectList(new QueryWrap<AppTemplate>().eq("is_del", 0));

        List<SubscribedApp> subscribedAppList =
            subscribedAppBaseMapper.selectList(new QueryWrap<SubscribedApp>().eq("is_del", 0));
        Map<Long, SubscribedApp> subAppMap = new HashMap<>();
        subscribedAppList.forEach(subscribedApp -> subAppMap.put(subscribedApp.getAppId(), subscribedApp));

        // Logo改为从resource读取
        ClassPathResource resource = new ClassPathResource(ObsConstant.DEFAULT_LOGO_PATH);
        MultipartFile multipartFile;
        try (InputStream is = resource.getInputStream()) {
            String fileName = resource.getFilename();
            if (StringUtils.isBlank(fileName)) {
                log.error("应用logo名称为空.");
                throw new BizException(ExceptionCode.ILLEGAL_ARGUMENT_EX);
            }

            multipartFile = new MockMultipartFile(fileName, fileName, null, is);
        } catch (IOException e) {
            log.error("读取应用logo时出错: {}", e.getMessage());
            throw new BizException(ExceptionCode.ILLEGAL_ARGUMENT_EX);
        }

        WorkSecretKey workSecretKey = workSecretKeyBaseMapper.getPlatformSalt();
        String pwdSalt = EncryptionUtil.decrypt(workSecretKey.getPwdSalt(), workSecretKey.getPassword());
        for (AppTemplate appTemplate : appTemplateList) {
            ObsAuthDto obsAuth = getObsAuthDto(pwdSalt, appTemplate.getId(),
                appTemplate.getArchitecture().equals(AppArchitectureEnum.MICROSERVICE.getType()));

            if (obsAuth == null) {
                log.warn("未找到应用<{}>的OBS信息,跳过当前应用", appTemplate.getId());
                continue;
            }

            // Logo更改为OBS访问链接
            appTemplate.setLogo(obsApi.upload(platformAppName + ObsConstant.OBS_FILE_DELIMITER + appTemplate.getId(),
                multipartFile, obsAuth, FileTypeEnum.PICTURE, FileSizeEnum.MEGABYTE, 1));

            appTemplateBaseMapper.updateById(appTemplate);

            if (subAppMap.containsKey(appTemplate.getId())) {
                subscribedAppBaseMapper.updateById(subAppMap.get(appTemplate.getId()).setLogo(appTemplate.getLogo()));
            }
        }

        log.info("应用初始Logo设置完成.");
    }

    /**
     * 清除OBS中的Logo文件
     */
    @Override
    public void deleteLogo() {
        log.info("开始删除所有应用Logo...");
        List<AppTemplate> appTemplateList =
            appTemplateBaseMapper.selectList(new QueryWrap<AppTemplate>().eq("is_del", 0));
        WorkSecretKey workSecretKey = workSecretKeyBaseMapper.getPlatformSalt();
        if (Objects.isNull(workSecretKey)) {
            log.info("删除所有应用Logo:查询salt异常");
            return;
        }
        String pwdSalt = EncryptionUtil.decrypt(workSecretKey.getPwdSalt(), workSecretKey.getPassword());
        for (AppTemplate appTemplate : appTemplateList) {
            ObsAuthDto obsAuth = getObsAuthDto(pwdSalt, appTemplate.getId(),
                appTemplate.getArchitecture().equals(AppArchitectureEnum.MICROSERVICE.getType()));

            if (obsAuth == null) {
                log.warn("未找到应用<{}>的OBS信息,跳过当前应用", appTemplate.getName());
                continue;
            }

            obsApi.deleteAllFile(obsAuth, platformAppName + ObsConstant.OBS_FILE_DELIMITER + appTemplate.getId());
            // 不同应用在同一个桶时, 共享同一个platformAppName+'/'父文件夹
            // 前一个应用删除父文件夹只是删除空对象, 不影响之后应用删除自己文件夹中的对象
            // 但调用删除platformAppName+'/'的方法时, 需要判断对象是否存在, 否则会报错
            obsApi.deleteFile(obsAuth, platformAppName + ObsConstant.OBS_FILE_DELIMITER);
        }

        log.info("应用Logo删除完成.");
    }

    /**
     * 删除OBS中工单相关数据
     */
    @Override
    public void deleteWorksheet() {
        log.info("开始删除工单相关数据...");
        ObsAuthDto obsAuthDto = getObsInfo();
        if (obsAuthDto == null) {
            log.info("未找到OBS相关信息，跳过删除工单相关数据.");
            return;
        }

        obsApi.deleteAllFile(obsAuthDto, platformAppName + ObsConstant.OBS_TMP_PREFIX);
        obsApi.deleteAllFile(obsAuthDto, platformAppName + ObsConstant.OBS_WORKSHEET_PREFIX);
        obsApi.deleteFile(obsAuthDto, platformAppName + ObsConstant.OBS_FILE_DELIMITER);

        log.info("工单相关数据删除完成.");
    }

    @Override
    public void initObs() {
        try {
            log.info("--------------开始初始化obs资源---------------");
            // 创建所有租户共享私有桶
            createShareObs();

            // 创建ISV独享私有桶
            createISVObs();
            log.info("--------------初始化obs资源完成---------------");
        } catch (Exception e) {
            log.error("------------初始化obs桶失败，error:{}----------", e.toString());
            throw new BizException(ExceptionCode.OBS_INIT_ERROR);
        }

    }

    /**
     * 创建ISV独享私有桶
     *
     * @throws Exception
     */
    private void createISVObs() throws Exception {
        String bucketName = "obs-" + platformAppName;
        log.info("---------------start：创建{}独享私有桶------------", platformAppName);
        obsApi.createBucket(ObsAuthDto.builder()
            .ak(iam.getAccessKey())
            .sk(iam.getSecretKey())
            .bucketName(bucketName)
            .endpoint(endpoint.getObs())
            .build(), iam.getRegion(), bucketName);

        obsResourceBaseMapper.insert(ObsResource.builder()
            .obsName(bucketName)
            .tenantCode(platformAppName)
            .type(FileSystemTypeEnum.OBS.getType())
            .strategy(ObsStrategyEnum.PRIVATE.getType())
            .build());
        log.info("---------------end：创建{}独享私有桶------------", platformAppName);
    }

    /**
     * 创建所有租户共享私有桶
     */
    private void createShareObs() throws Exception {
        log.info("---------------start：创建所有租户共享私有桶------------");
        String bucketName = obs.getBucketName();
        obsApi.createBucket(ObsAuthDto.builder()
            .ak(iam.getAccessKey())
            .sk(iam.getSecretKey())
            .bucketName(bucketName)
            .endpoint(endpoint.getObs())
            .build(), iam.getRegion(), bucketName);

        obsResourceBaseMapper.insert(ObsResource.builder()
            .obsName(bucketName)
            .type(FileSystemTypeEnum.OBS.getType())
            .strategy(ObsStrategyEnum.PRIVATE.getType())
            .build());
        log.info("---------------end：创建所有租户共享私有桶------------");
    }

    @Override
    public void deleteObs() {
        log.info("-------------start：格式化obs----------");
        // 删除桶是否全部成功
        AtomicBoolean isDeleteSuccess = new AtomicBoolean(true);

        List<ObsResource> obsResources = obsResourceBaseMapper.selectList(
            new LambdaQueryWrapper<ObsResource>().eq(ObsResource::getType, FileSystemTypeEnum.OBS.getType()));

        if (CollectionUtil.isNotEmpty(obsResources)) {
            obsResources.stream()
                .filter(obsResource -> StringUtils.isNotBlank(obsResource.getObsName()))
                .forEach(obsResource -> {
                    String obsName = obsResource.getObsName();
                    try {
                        ObsAuthDto authDto = ObsAuthDto.builder()
                            .endpoint(endpoint.getObs())
                            .bucketName(obsName)
                            .sk(iam.getSecretKey())
                            .ak(iam.getAccessKey())
                            .build();

                        // 清空桶内所有对象
                        obsApi.deleteAllFile(authDto, null);

                        // 删除桶
                        obsApi.deleteBucket(authDto, obsName);
                    } catch (IOException e) {
                        isDeleteSuccess.set(false);
                        log.error("！！！！！！！！！！！ {}  私有桶删除失败！！！！！！！！！！", obsName);
                    }
                });
        }
        if (!isDeleteSuccess.get()) {
            throw new BizException(ExceptionCode.OBS_DELETE_ERROR);
        }
        log.info("--------------end：格式化obs-------------");
    }

    /**
     * 获取OBS信息
     *
     * @param pwdSalt 盐
     * @param appId 应用ID
     * @param isMicro 是否为微服务架构应用
     * @return OBS信息
     */
    private ObsAuthDto getObsAuthDto(String pwdSalt, Long appId, Boolean isMicro) {
        ObsInfoView obsInfo = obsInfoBaseMapper.getObsInfoByApp(appId, isMicro);
        if (obsInfo == null) {
            return null;
        }

        return BeanPlusUtil.toBean(obsInfo.setAk(EncryptionUtil.decrypt(pwdSalt, obsInfo.getAk()))
            .setSk(EncryptionUtil.decrypt(pwdSalt, obsInfo.getSk())), ObsAuthDto.class);
    }

    /**
     * 获取OBS信息
     *
     * @return OBS信息
     */
    private ObsAuthDto getObsInfo() {
        ContextUtil.setTenantApp(platformAppName);

        WorkSecretKey workSecretKey = workSecretKeyBaseMapper.getPlatformSalt();
        if (workSecretKey == null) {
            return null;
        }
        String pwdSalt = EncryptionUtil.decrypt(workSecretKey.getPwdSalt(), workSecretKey.getPassword());

        ObsInfoView obsInfo = obsInfoBaseMapper.getObsInfoDto();
        if (obsInfo == null) {
            return null;
        }

        return BeanPlusUtil.toBean(obsInfo.setAk(EncryptionUtil.decrypt(pwdSalt, obsInfo.getAk()))
            .setSk(EncryptionUtil.decrypt(pwdSalt, obsInfo.getSk())), ObsAuthDto.class);
    }
}
